extends Line2D

# ECG-style heartbeat line with a mostly flat baseline and pronounced up/down spikes (P/QRS/T).
# Attach this node (or instantiate from code) and place it where you want the line to appear.

@export var line_length: float = 900.0     # Visible length of the line in pixels
@export var amplitude: float = 60.0        # Base height of the up spike
@export var spike_count: int = 1           # Number of spike pairs drawn within one repeated cycle
@export var frequency: float = 0.6         # Cycles per second for the scrolling effect (slower by default)
@export var minor_wave_mix: float = 0.0    # Baseline jitter ratio (keep 0 for a flat baseline)
@export var points_count: int = 360        # Sample count for the line
@export var baseline: float = 0.0          # Baseline offset; positive moves the whole line up

# 音频驱动的振幅调制（根据 BGM 频段能量）
@export var audio_bus_name: String = ""    # 留空则尝试读取 MusicPlayer.bgm_player.bus，失败则用 Master
@export var audio_freq_low: float = 60.0    # 低频采样下限 (Hz)
@export var audio_freq_high: float = 2000.0 # 高频采样上限 (Hz)
@export var audio_amp_min: float = 0.4      # 振幅缩放下限（避免静音时完全拉平）
@export var audio_amp_max: float = 10   # 振幅缩放上限（避免过大抖动）
@export var audio_energy_gain: float = 20.0  # 能量映射增益（更敏感）
@export var audio_response_speed: float = 10.0 # 对振幅变化的响应速度

var _phase: float = 0.0
var _amp_mul: float = 1.0
var _analyzer: AudioEffectSpectrumAnalyzerInstance
var _bus_idx: int = -1
var _analyzer_idx: int = -1
var _target_amp: float = 1.0

func _ready() -> void:
	width = 3.0
	antialiased = true
	texture_mode = Line2D.LINE_TEXTURE_NONE
	default_color = Color(1.0, 0.0, 0.0, 1.0)
	_setup_analyzer()
	_rebuild()

func _process(delta: float) -> void:
	_update_amp_from_audio(delta)
	_phase = fposmod(_phase + delta * max(frequency, 0.0), 1.0)
	_rebuild()

func _rebuild() -> void:
	var pts := PackedVector2Array()
	pts.resize(points_count)
	var inv = 1.0 / float(max(points_count - 1, 1))
	for i in range(points_count):
		var u = float(i) * inv
		var sample_u = fposmod(u - _phase, 1.0)
		var y = _sample(sample_u)
		pts[i] = Vector2(u * line_length, -y)  # negative so positive values draw upward
	points = pts

func _sample(u: float) -> float:
	# Draw spike pairs at fixed positions along the line (no scrolling baseline).
	var beat_spacing = 1.0 / float(max(spike_count, 1))
	var v = 0.0
	var base_amp = amplitude * _amp_mul
	for i in range(spike_count):
		var local_u = fposmod(u - beat_spacing * i + 1.0, 1.0)
		v += _spike_pair(local_u, base_amp)

	# Optional tiny jitter (default 0 => perfectly flat baseline outside spikes)
	var jitter = 0.0
	if minor_wave_mix > 0.0:
		jitter = sin(u * TAU * 3.0) * base_amp * minor_wave_mix

	return v + jitter + baseline


func _spike_pair(u: float, base_amp: float) -> float:
	# One up spike followed immediately by a down spike (ECG-like up/down pair on a flat baseline).
	# Keep both spikes near the center region to avoid edge clutter.
	var up = _tri(u, 0.48, 0.05, base_amp)
	var down = _tri(u, 0.52, 0.05, -base_amp * 0.8)
	return up + down


func _tri(u: float, center: float, width: float, height: float) -> float:
	var d = abs(fposmod(u - center + 1.0, 1.0))
	d = min(d, 1.0 - d)
	var half_w = width * 0.5
	if d >= half_w:
		return 0.0
	var t = 1.0 - d / half_w
	return t * height


func _setup_analyzer() -> void:
	# Choose bus from MusicPlayer if available, else use provided name or Master
	var bus_name = audio_bus_name
	if bus_name == "":
		var mp = get_node_or_null("/root/MusicPlayer")
		if mp and mp.has_method("play_bgm"):
			var player = mp.get_node_or_null("BGMPlayer")
			if player and player.has_method("get_bus"):
				bus_name = player.bus
	if bus_name == "":
		bus_name = "Master"
	_bus_idx = AudioServer.get_bus_index(bus_name)
	if _bus_idx == -1:
		_bus_idx = 0

	_analyzer_idx = _find_analyzer(_bus_idx)
	if _analyzer_idx == -1:
		var eff := AudioEffectSpectrumAnalyzer.new()
		_analyzer_idx = AudioServer.get_bus_effect_count(_bus_idx)
		AudioServer.add_bus_effect(_bus_idx, eff)

	_analyzer = AudioServer.get_bus_effect_instance(_bus_idx, _analyzer_idx) as AudioEffectSpectrumAnalyzerInstance


func _find_analyzer(bus_idx: int) -> int:
	var count = AudioServer.get_bus_effect_count(bus_idx)
	for i in range(count):
		var eff = AudioServer.get_bus_effect(bus_idx, i)
		if eff is AudioEffectSpectrumAnalyzer:
			return i
	return -1


func _update_amp_from_audio(delta: float) -> void:
	if _analyzer == null:
		_amp_mul = 1.0
		return
	var mag: Vector2 = _analyzer.get_magnitude_for_frequency_range(audio_freq_low, audio_freq_high, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_AVERAGE)
	# mag.x/y are linear magnitudes (0..~1). Use length for both channels.
	var energy = mag.length()
	var normalized = clamp(energy * audio_energy_gain, 0.0, 1.0)
	_target_amp = lerp(audio_amp_min, audio_amp_max, normalized)
	var lerp_factor = clamp(delta * audio_response_speed, 0.0, 1.0)
	_amp_mul = lerp(_amp_mul, _target_amp, lerp_factor)
